// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Utils
{
	/// <summary>
	/// Provides functionality that can be used for weak events.
	/// </summary>
	/// <typeparam name="TEventArgs">The event args type of the weak event.</typeparam>
	public class WeakEvent<TEventArgs>
		where TEventArgs : EventArgs
	{
		/// <summary>
		/// Holds the method and the instance to perform a callback in a WeakReference.
		/// </summary>
		class InvocationInfo
		{
			public InvocationInfo(MethodInfo method, WeakReference reference, int targetHashCode)
			{
				_targetHashCode = targetHashCode;
				Method = method;
				Reference = reference;
			}

			private readonly int _targetHashCode;

			public MethodInfo Method { get; private set; }
			public WeakReference Reference { get; private set; }

			public long Key
			{
				get { return _targetHashCode + (long)Method.GetHashCode(); }
			}
		}

		private readonly IDictionary<long, InvocationInfo> _weakEventHandlerList
			= new Dictionary<long, InvocationInfo>();

		/// <summary>
		/// Adds an event handler to the list of weak event handlers.
		/// </summary>
		/// <remarks>
		/// Multiple Registrations for the same instance and method are prevented.
		/// </remarks>
		public void AddHandler(Delegate eventHandler)
		{
			lock (_weakEventHandlerList)
			{
				var invocationInfo = new InvocationInfo(
					eventHandler.Method,
					new WeakReference(eventHandler.Target),
					eventHandler.Target.GetHashCode());

				if (!_weakEventHandlerList.ContainsKey(invocationInfo.Key))
				{
					_weakEventHandlerList.Add(
						invocationInfo.Key,
						invocationInfo);
				}
			}
		}

		/// <summary>
		/// Removes an event handler from the list of weak event handlers.
		/// </summary>
		/// <remarks>
		/// If the event handler was not registered, no exception will be thrown.
		/// </remarks>
		public void RemoveHandler(Delegate eventHandler)
		{
			lock (_weakEventHandlerList)
			{
				var invocationInfo = new InvocationInfo(
					eventHandler.Method,
					new WeakReference(eventHandler.Target),
					eventHandler.Target.GetHashCode());
				_weakEventHandlerList.Remove(invocationInfo.Key);
			}
		}

		/// <summary>
		/// Calls the currently present callbacks that were added to the list of weak event handlers.
		/// </summary>
		public void RaiseEvent(object sender, TEventArgs e)
		{
			lock (_weakEventHandlerList)
			{
				var referencesToRemove = new List<InvocationInfo>();

				// Raise the events
				foreach (var it in _weakEventHandlerList.Values)
				{
					// obtain a strong reference
					var target = it.Reference.Target;
					if (target != null)
					{
						it.Method.Invoke(
							target,
							new[] { sender, e });
					}
					else
						referencesToRemove.Add(it);
				}

				RemoveUnusedReferences(referencesToRemove);
			}
		}

		/// <summary>
		/// Removes all event handlers that have already been collected by the GC.
		/// </summary>
		private void RemoveUnusedReferences(IEnumerable<InvocationInfo> referencesToRemove)
		{
			foreach (var it in referencesToRemove)
				_weakEventHandlerList.Remove(it.Key);
		}
	}
}